package com.shelpe.services.lexicon.controller;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.shelpe.services.lexicon.combine.rule.CombineRule;
import com.shelpe.services.lexicon.constants.Status;
import com.shelpe.services.lexicon.dto.AdgroupRecordDto;
import com.shelpe.services.lexicon.entity.AdgroupRecord;
import com.shelpe.services.lexicon.entity.AdgroupRecord.SyncType;
import com.shelpe.services.lexicon.entity.KeywordRecommend;
import com.shelpe.services.lexicon.properties.AutoDriveParams;
import com.shelpe.services.lexicon.repository.mongo.AdgroupRecordRepository;
import com.shelpe.services.lexicon.repository.mongo.KeywordRecommendRepository;
import com.shelpe.services.lexicon.service.AdgroupRecordService;
import com.shelpe.services.lexicon.service.InsightMetricService;
import com.shelpe.services.lexicon.service.KeywordRecommendService;
import com.shelpe.services.lexicon.simba.SimbaProxyBaseDto;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Controller
@RequestMapping("/lexiconservice/adgroups/{adgroup_id}")
@Slf4j
public class AdgroupController {

	@Autowired
	private AdgroupRecordRepository adgroupRecordRepository;
	@Autowired
	private AdgroupRecordService adgroupRecordService;
	@Autowired
	private KeywordRecommendService keywordRecommendService;
	@Autowired
	private InsightMetricService insightMetricService;
	@Autowired
	private KeywordRecommendRepository keywordRecommendRepository;
	@Autowired
	private AutoDriveParams params;
	@Autowired
	private CombineRule combineRule;
	
	@RequestMapping(method = RequestMethod.POST, path="/records")
	@ResponseBody
	public DeferredResult<Response> create(final @PathVariable("adgroup_id")long adgroupID, @RequestBody AdgroupRecordDto dto){
		final DeferredResult<Response> result = new DeferredResult<Response>();
		
		Response timeoutRet = new Response();
		timeoutRet.setError(new SimbaProxyBaseDto.Error());
		timeoutRet.getError().setCode(500);
		timeoutRet.getError().setMessage("timeout");
		timeoutRet.getError().setSubCode("timeout");
		timeoutRet.getError().setSubMessage("超时");
		result.onTimeout(new Runnable(){
			@Override
			public void run() {
				log.error("timeout createAdgroupRecord {} {}", adgroupID);
			}
		});
		
		final Response res = new Response();
		res.setAdgroupID(adgroupID);
		
		AdgroupRecord existingRecord = null;
		List<AdgroupRecord> records = this.adgroupRecordRepository.findByAdgroupID(adgroupID, new Sort(new Order(Direction.DESC, "postTime")));
		if(records != null){
			for(AdgroupRecord ar: records){
				if(ar.getStatus() != Status.fail){
					existingRecord = ar;
					break;
				}
			}
		}
		if(existingRecord != null && existingRecord.getLastModifiedTime() != null && DateUtils.isSameDay(new Date(), existingRecord.getLastModifiedTime())){
			int recommendCount = this.keywordRecommendRepository.countByAdgroupRecordID(existingRecord.getRecordID());
			if(recommendCount > 0){
				log.info("the record is exist with {} recommends: {}", existingRecord, recommendCount);
				res.setAdgroupRecordID(existingRecord.getRecordID());
				res.setStatus(true);
				result.setResult(res);
				return result;
			}
		}
		
		final AdgroupRecord record = this.adgroupRecordService.createFromAdgroup(adgroupID);
		
		res.setAdgroupRecordID(record.getRecordID());
		if(dto.isNeedSync()){
			log.info("start to sync data: {}", record);
			final AtomicInteger completedCounter = new AtomicInteger();
			this.keywordRecommendService.syncRecommends(record, new KeywordRecommendService.Callback(){

				@Override
				public void call(List<KeywordRecommend> recommends, SimbaProxyBaseDto.Error error) {
					if(recommends != null && recommends.size() > 0){
						record.setStatus(Status.syncing);
						log.info("finish to sync data: {}", record);
						record.setRecommendCount(recommends.size());
					}else{
						log.error("no recommends: {}", record);
						res.setError(error);
					}
					record.setSyncType(SyncType.active);
					record.setLastModifiedTime(new Date());
					adgroupRecordRepository.save(record);
					
					completedCounter.incrementAndGet();
					
					if(completedCounter.get() == 2){
						setupResult(res, record);
						result.setResult(res);
					}
				}
				
			});
			combineRule.combine(record);
			completedCounter.incrementAndGet();
			if(completedCounter.get() == 2){
				setupResult(res, record);
				result.setResult(res);
			}
		}else{
			res.setStatus(true);
			result.setResult(res);
		}
		
		return result;
	}
	
	private void setupResult(Response res, AdgroupRecord record){
		int recommendCount = keywordRecommendRepository.countByAdgroupRecordID(record.getRecordID());
		record.setRecommendCount(recommendCount);
		record.setStatus(Status.syncing);
		record.setLastModifiedTime(new Date());
		adgroupRecordRepository.save(record);
		res.setStatus(recommendCount > 0);
	}
	
	@RequestMapping(method = RequestMethod.POST, path="/metrics")
	@ResponseBody
	public DeferredResult<Response> metrics(@PathVariable("adgroup_id")final long adgroupID, @RequestBody(required=false) AdgroupMetricDto dto){
		final DeferredResult<Response> result = new DeferredResult<Response>();
		
		
		final AdgroupRecord record = this.findLastRecord(adgroupID);
		if(record.getStatus() != Status.init
				&& record.getLastModifiedTime() != null && DateUtils.isSameDay(new Date(), record.getLastModifiedTime())){
			long yesterdayGlobalInsightCount = this.insightMetricService.countByAdgroupRecordIDAndGlobalAndScopeEndTime(record.getRecordID(), true,
					DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -1));
			if(yesterdayGlobalInsightCount > 0){
				log.info("yesterday insight is already synced with {} global data: {}", yesterdayGlobalInsightCount, record);
				Response res = new Response();
				res.setAdgroupID(adgroupID);
				res.setAdgroupRecordID(record.getRecordID());
				res.setStatus(true);
				result.setResult(res);
				return result;
			}
		}

		final List<KeywordRecommend> recommends = this.keywordRecommendRepository.findByAdgroupRecordID(record.getRecordID());
		log.debug("need {} words  to sync metric from taobao.", recommends.size());
		Date yesterday = DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -1);
		
		InsightMetricService.Callback callback = new InsightMetricService.Callback() {
			@Override
			public void call(List<KeywordRecommend> recs) {
				for(KeywordRecommend recommend: recommends){
					recommend.setLastModifiedTime(new Date());
				}
				keywordRecommendRepository.save(recommends);
				
				log.debug("finish to sync data");
				record.setSyncType(SyncType.active);
				record.setStatus(Status.sync);
				record.setMerged(true);
				record.setLastModifiedTime(new Date());
				adgroupRecordRepository.save(record);
				
				Response res = new Response();
				res.setAdgroupID(adgroupID);
				res.setAdgroupRecordID(record.getRecordID());
				res.setStatus(true);
				result.setResult(res);
			}
		};
		
		
		if(dto == null){
			dto = new AdgroupMetricDto();
		}
		if(dto.syncCategory && dto.syncDevice){
			log.debug("sync global, category, network metric", record);
			this.insightMetricService.syncInsightData4Recommends(recommends, 
					DateUtils.addDays(yesterday, -params.getStateDays()), yesterday, 
					record, callback);
		}else if(dto.syncCategory){
			log.debug("sync global, category metric", record);
			this.insightMetricService.syncGlobalAndCategoryInsightData4Recommends(recommends, 
					DateUtils.addDays(yesterday, -params.getStateDays()), yesterday, 
					record, callback);
		}else{
			log.debug("sync global metric", record);
			this.insightMetricService.syncGlobalInsightData4Recommends(recommends, 
					DateUtils.addDays(yesterday, -params.getStateDays()), yesterday,
					record, callback);
		}
		return result;
	}
	
	@Data
	public static class AdgroupMetricDto {
		@JsonProperty("sync_category")
		private boolean syncCategory = true;
		@JsonProperty("sync_device")
		private boolean syncDevice = true;
	}

	
	@RequestMapping(method = RequestMethod.POST, path="/tags")
	@ResponseBody
	public Response tags(@PathVariable("adgroup_id")long adgroupID){
		AdgroupRecord record = this.findLastSyncRecord(adgroupID);

		List<KeywordRecommend> recommends = this.keywordRecommendRepository.findByAdgroupRecordID(record.getRecordID());

		log.debug("start to fill up tags for {} words", recommends.size());
		this.keywordRecommendService.syncRecommendTags(recommends, record);
		
		record.setStatus(Status.calcing);
		record.setCalcTime(new Date());
		record.setTagTime(new Date());
		record.setLastModifiedTime(new Date());
		this.adgroupRecordRepository.save(record);
		
		Response res = new Response();
		res.setAdgroupID(adgroupID);
		res.setAdgroupRecordID(record.getRecordID());
		res.setStatus(true);
		return res;
	}
	
	@RequestMapping(method = RequestMethod.GET, path="/@merge")
	@ResponseBody
	public void merge(){
		while(true){
			List<AdgroupRecord> records = this.adgroupRecordRepository.findTop100ByMergedIsNullOrderByPostTimeDesc();
			if(records.size() == 0){
				return;
			}
			
			for(AdgroupRecord record: records){
				log.debug("merge record: {}", record);
				this.insightMetricService.merge2WordInsightMetric(record);
			}
		}
	}
	
	private AdgroupRecord findLastRecord(long adgroupID){
		AdgroupRecord record = null;
		List<AdgroupRecord> records = this.adgroupRecordRepository.findByAdgroupID(adgroupID, new Sort(new Order(Direction.DESC, "postTime")));
		if(records != null){
			for(AdgroupRecord ar: records){
				if(ar.getStatus() != Status.fail){
					record = ar;
					break;
				}
			}
		}
		if(record == null){
			throw new IllegalArgumentException("record_not_found");
		}
		return record;
	}
	
	private AdgroupRecord findLastSyncRecord(long adgroupID){
		AdgroupRecord record = null;
		List<Status> statuses = new ArrayList<Status>();
		statuses.add(Status.calcing);
		statuses.add(Status.sync);
		statuses.add(Status.calc);
		List<AdgroupRecord> records = this.adgroupRecordRepository.findByAdgroupIDAndStatusIn(adgroupID, statuses, new Sort(new Order(Direction.DESC, "postTime")));
		if(records != null){
			if(records.size() > 0){
				record = records.get(0);
			}
		}
		if(record == null){
			throw new IllegalArgumentException("record_not_found");
		}
		return record;
	}
	
	@Data
	@JsonInclude(Include.NON_NULL)
	public static class Response{
		private long adgroupID;
		private String adgroupRecordID;
		private boolean status;
		
		private SimbaProxyBaseDto.Error error;
	}
}
